// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.jasmin.unit.z80.v1;

import com.dustedpixels.common.StringUtils;
import com.dustedpixels.jasmin.unit.Unit;
import com.dustedpixels.jasmin.unit.tools.Oscilloscope;
import com.dustedpixels.jasmin.unit.tools.Pin;

/**
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public class Z80 implements Unit {
  public boolean CLK;
  public int ADDR;
  public int DATA;
  public boolean M1;
  public boolean RD;
  public boolean MREQ;
  public boolean RFSH;
  public boolean WAIT;
  public boolean RESET;
  
  private int irRegister;
  private int mCycle = 0;
  private int iCycle = 0;
  private int instrRegister = 0;
  private boolean decodeSignal;
  
  final static int MCYCLE_FETCH = 0;
  final static int MCYCLE_READ = 1;
  final static int MCYCLE_WRITE = 2;
  int mCycleType = MCYCLE_FETCH;
  
  private int addrSignal = 0;
  private int dataSignal = 0;
  private boolean m1Signal = false;
  private boolean rdSignal = false;
  private boolean rdFlipFlop = false;
  private boolean mreqSignal = false;
  private boolean mreqFlipFlop = false;
  private boolean rfshSignal = false;
  
  private int tmpRegister = 0;
  private int accumulator = 100;
  private int tmpAccumulator = 0;
  
  final Regs regs;
  
  int regsMux;
  final static int REGS_MUX_NONE = 0x00;
  final static int REGS_MUX_DATA = 0x01;
  final static int REGS_MUX_ADDR = 0x02;
  final static int REGS_MUX_INC  = 0x03;
  
  int addrMux;
  final static int ADDR_MUX_NONE = 0x00;
  
  int dataMux;
  final static int DATA_MUX_NONE = 0x00;
  final static int DATA_MUX_ACC = 0x01;
  final static int DATA_MUX_TMP = 0x02;
  final static int DATA_MUX_REGS = 0x03;
  
  public Z80() {
    this(new Regs());
  }
  
  Z80(Regs regs) {
    this.regs = regs;
  }
  
  /**
   * Updates pins. Assumption: clock signal changed.
   */
  public void updateEndPoints() {
    
    /*------------------------*
     * MACHINE CYCLE HANDLING *
     *------------------------*/
    if (!CLK) {
      // This occurs half-cycle before the public one.
      mreqSignal = false;
      rdSignal = false;
      m1Signal = false;
      rfshSignal = false;
      decodeSignal = false;
      
      regsMux = REGS_MUX_NONE;
      addrMux = ADDR_MUX_NONE;
      dataMux = DATA_MUX_NONE;
      
      switch (mCycleType) {
        case MCYCLE_FETCH:
          switch (mCycle) {
            case 0:
              // Deposit PC register on internal address bus for ADDR buffer
              regs.REG_OUT = Regs.REG_PC;
              regs.MODE_OUT = Regs.MODE_16BIT;
              regs.updateComb();
              addrSignal = regs.DATA_OUT;
              
              // Update internal control bus
              m1Signal = true;
              break;
            case 1:
              // Deposit PC register on internal address bus for increment.
              regs.REG_OUT = Regs.REG_PC;
              regs.MODE_OUT = Regs.MODE_16BIT;
              regs.updateComb();
              addrSignal = regs.DATA_OUT;

              regs.REG_IN = Regs.REG_PC;
              regs.MODE_IN = Regs.MODE_16BIT;
              
              assert (regsMux == REGS_MUX_NONE);
              regsMux = REGS_MUX_INC;

              // Update internal control bus
              mreqSignal = true;
              rdSignal = true;
              break;
            case 2:
              addrSignal = irRegister;
              rfshSignal = true;
              break;
            case 3:
              mreqSignal = true;
              decodeSignal = true;
              break;
          }
          break;
        case MCYCLE_READ:
          switch (mCycle) {
            case 0:
              break;
            case 1:
              break;
            case 2:
              break;
          }
          break;
        case MCYCLE_WRITE:
          switch (mCycle) {
            case 0:
              break;
            case 1:
              break;
            case 2:
              break;
          }
          break;
      }
    } else { // if (CLK)
      if (RESET) {
        mCycleType = MCYCLE_FETCH;
        mCycle = 0;
      } else {
        switch (mCycleType) {
          case MCYCLE_FETCH:
            switch (mCycle) {
              case 0:
                ADDR = addrSignal;
                M1 = m1Signal;
                RFSH = rfshSignal;
                mCycle++;
                break;
              case 1:
                mreqFlipFlop = mreqSignal;
                rdFlipFlop = rdSignal;
                mCycle++;
                break;
              case 2:
                if (!WAIT) {
                  mreqFlipFlop = mreqSignal;
                  rdFlipFlop = rdSignal;
                  instrRegister = DATA;
                  iCycle = 0;
                  M1 = m1Signal;
                  ADDR = addrSignal;
                  RFSH = rfshSignal;                  
                  mCycle++;
                }
                break;
              case 3:
                mCycle = 0;
                break;
            }
            
            break;
          case MCYCLE_READ:
            switch (mCycle) {
              case 0:
                break;
              case 1:
                break;
              case 2:
                break;
            }
            break;
          case MCYCLE_WRITE:
            switch (mCycle) {
              case 0:
                break;
              case 1:
                break;
              case 2:
                break;
            }
            break;
        } // switch (mCycleType)
      }
    } // if (CLK)
        
    // Instruction decoder
    if (decodeSignal) {
      switch (instrRegister) {
        case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45:
        case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D:
        case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55:
        case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D:
        case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65:
        case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D:
          // LD r,r' (B,C,D,E,H,L only)
          if (!CLK) {
            regs.REG_OUT = instrRegister & 0x07;
            regs.MODE_OUT = Regs.MODE_8BIT;
            regs.REG_IN = (instrRegister >>> 3) & 0x07;
            regs.MODE_IN = Regs.MODE_8BIT;
            regs.updateComb();
            dataSignal = regs.DATA_OUT;
            
            assert (regsMux == REGS_MUX_NONE);
            regsMux = REGS_MUX_DATA;
          } else {
            instrRegister = 0;
          }
          break;
        case 0x47: case 0x4F: case 0x57:case 0x5F:case 0x67:case 0x6F:
          // LD r,A
          if (!CLK) {
            assert (dataMux == DATA_MUX_NONE);
            dataMux = DATA_MUX_ACC;
            
            regs.REG_IN = (instrRegister >>> 3) & 0x07;
            regs.MODE_IN = Regs.MODE_8BIT;
            
            assert (regsMux == REGS_MUX_NONE);
            regsMux = REGS_MUX_DATA;
          } else {
            instrRegister = 0;
          }
          break;
        case 0x78: case 0x79: case 0x7A:case 0x7B:case 0x7C:case 0x7D:
          // LD A,r'
          if (!CLK) {
            regs.REG_OUT = instrRegister & 0x07;
            regs.MODE_OUT = Regs.MODE_8BIT;
            regs.updateComb();
            
            assert(dataMux == DATA_MUX_NONE);
            dataMux = DATA_MUX_REGS;
          } else {
            accumulator = dataSignal;
            instrRegister = 0;
          }
          break;
        case 0x06: case 0x0E: case 0x16: case 0x1E: case 0x26: case 0x2E:
          // LD r,n
          if (!CLK) {
            switch (iCycle) {
              case 0:
                mCycleType = MCYCLE_READ;
                break;
              case 1:
                break;
            }
          } else {
            
          }
      }
    }
  
    // Data bus
    if (!CLK) {
      switch(dataMux) {
        case DATA_MUX_NONE:
          break;
        case DATA_MUX_ACC:
          dataSignal = accumulator;
          break;
        case DATA_MUX_TMP:
          dataSignal = tmpRegister;
          break;
        case DATA_MUX_REGS:
          dataSignal = regs.DATA_OUT;
          break;
      }
    }
    
    // Registers
    if (!CLK) {
      switch (regsMux) {
        case REGS_MUX_NONE:
          break;
        case REGS_MUX_DATA:
          regs.DATA_IN = dataSignal;
          break;
        case REGS_MUX_ADDR:
          regs.DATA_IN = addrSignal;
          break;
        case REGS_MUX_INC:
          regs.DATA_IN = (addrSignal + 1) & 0xFFFF;
          break;
      }
      regs.RESET = RESET;
    } else {
      if (RESET | (regsMux != REGS_MUX_NONE)) {
        regs.updateSeq();
      }
    }

    MREQ = mreqSignal || mreqFlipFlop;
    RD = rdSignal || rdFlipFlop;    
  }
  
  public static void main(String[] args) throws Exception {
    Regs regs = new Regs();
    Z80 z80 = new Z80(regs);
    
    Oscilloscope osc = new Oscilloscope(
        Pin.of("CLK", z80, "CLK"),
        Pin.of("RESET", z80, "RESET"),
        Pin.of("WAIT", z80, "WAIT"),
        Pin.of("ADDR", z80, "ADDR"),
        Pin.of("DATA", z80, "DATA"),
        Pin.of("MREQ", z80, "MREQ"),
        Pin.of("RD", z80, "RD"),
        Pin.of("M1", z80, "M1"),
        Pin.of("RFSH", z80, "RFSH")
    );
    
    for (int i = 0; i < 8 + 8*8; i++) {
      z80.CLK = (i & 0x01) == 0;
      z80.DATA = 0x58;
      //z80.WAIT = (i % 7) > 2; 
      z80.RESET = i < 7;
      z80.updateEndPoints();
      osc.sample();
    }
    
    osc.print(7);
   
    for (int i = 3500000 * 2 * 4; i != 0; i--) {
      z80.updateEndPoints();
      z80.CLK = !z80.CLK;
    }
    long start = System.currentTimeMillis();
    for (int i = 3500000 * 2 * 4; i != 0; i--) {
      z80.updateEndPoints();
      z80.CLK = !z80.CLK;
    }
    System.out.println("Completed 3500000 cycles in: " + 
        ((System.currentTimeMillis() - start) / 4) + "ms");
    
    System.out.println("A = " + StringUtils.hex8(z80.accumulator));
    regs.printState();
  }
}
